/*
 * Copyright (c) 2024 Guo Tingjin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * 防抖装饰器
 * @param interval 延迟执行时间 - ms
 */
export const Debounce = (interval: number) => {
  return (_: any, __: string, propertyDescriptor: PropertyDescriptor) => {
    const method = propertyDescriptor.value;
    if (method !== undefined) {
      let timer: number = 0;
      propertyDescriptor.value = function (...args: any[]) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          method.apply(this, args);
        }, interval);
      }
    }
  }
}

/**
 * 防抖，并以期约形式返回原函数的返回值，需要将原函数改为异步函数
 * @param interval 延迟执行时间 - ms
 * @example
 * // 使用该装饰器前
 * function hello(): number { return 1; }
 * // 使用该装饰器后
 * async function hello(): Promise<number> { return 1; }
 */
export const DebounceWithResult = <T>(interval:number)=>{
  return (_: any, __: string, propertyDescriptor: PropertyDescriptor) => {
    const method = propertyDescriptor.value;
    if (method !== undefined) {
      let timer: number = 0;
      propertyDescriptor.value = function (...args: any[]) {
        return new Promise<T>((resolve,reject)=>{
          clearTimeout(timer);
          timer = setTimeout(async () => {
            try {
              const result = await method.apply(this, args);
              resolve(result);
            }catch(e){
              reject(e);
            }
          }, interval);
        })
      }
    }
  }
}

export const Throttle = (interval:number, leading:boolean=true, trailing:boolean=false) => {
  return (_: any, __: string, propertyDescriptor: PropertyDescriptor) => {
    const method = propertyDescriptor.value;
    if (method !== undefined) {
      let timer: number = undefined;
      let lastTime = 0;
      propertyDescriptor.value = function (...args: any[]) {
        const now = Date.now();
        if(!lastTime && !leading) lastTime=now;
        const remainTime = interval - (now-lastTime);
        if(remainTime<=0){
          if(timer){
            clearTimeout(timer);
            timer=undefined;
          }
          method.apply(this,args);
          lastTime=now;
          return;
        }
        if(trailing && !timer) {
          timer=setTimeout(()=>{
            timer=undefined;
            lastTime=!leading?0:Date.now();
            method.apply(this,args);
          },remainTime);
        }
      }
    }
  }
}

export const ThrottleWithResult = <T>(interval:number, leading:boolean=true, trailing:boolean=false) => {
  return (_: any, __: string, propertyDescriptor: PropertyDescriptor) => {
    const method = propertyDescriptor.value;
    if (method !== undefined) {
      let timer: number = undefined;
      let lastTime = 0;
      propertyDescriptor.value = function (...args: any[]) {
        return new Promise<T>((resolve,reject)=>{
          try{
            const now = Date.now();
            if(!lastTime && !leading) lastTime=now;
            const remainTime = interval - (now-lastTime);
            if(remainTime<=0){
              if(timer){
                clearTimeout(timer);
                timer=undefined;
              }
              const result: T = method.apply(this,args);
              resolve(result);
              lastTime=now;
              return;
            }
            if(trailing && !timer) {
              timer=setTimeout(()=>{
                timer=undefined;
                lastTime=!leading?0:Date.now();
                const result: T = method.apply(this,args);
                resolve(result);
              },remainTime);
            }
          } catch (e) {
            reject(e);
          }
        })
      }
    }
  }
}